﻿using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;

namespace UtilsExtensionsAndHelpers.Helpers.Implimentations
{
    /// <summary>
    /// This is a helper class to serialization easy pie.
    /// just call either the Serialize or Deserialize
    /// methods. Enjoy! :)
    /// </summary>
    public class SerializationHelper : ISerializationHelper
    {
        private Action<String> _feedbackProvider;
        /// <summary>
        /// Get or set the action that provides feedback. 
        /// A message (string) will be passed in.
        /// IE. Log(message), Console.WriteLine(message)
        /// </summary>
        public Action<String> FeedbackProvider
        {
            get { return _feedbackProvider ?? Console.WriteLine; }
            set { _feedbackProvider = value; }
        }

        #region Serialization Methods
        /// <summary>
        /// Serializes an object.
        /// </summary>
        /// <param name="model">The object you want to Serialize</param>
        /// <returns>The serialized xml in the form of a string.</returns>
        public String Serialize(Object model)
        {
            String xmlSerializedString = null;

            MemoryStream memoryStream = null;
            XmlTextWriter xmlTextWriter = null;
            try
            {
                memoryStream = new MemoryStream();
                xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
                var serializer = new XmlSerializer(model.GetType());
                
                serializer.Serialize(xmlTextWriter, model);
                memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
                xmlSerializedString = UTF8ByteArrayToString(memoryStream.ToArray());
                xmlSerializedString = xmlSerializedString.Substring(xmlSerializedString.IndexOf("<"), xmlSerializedString.LastIndexOf(">")); ;
            }
            catch (Exception e)
            {
                FeedbackProvider(e.Message);
            }
            finally
            {
                if (memoryStream != null) memoryStream.Dispose();
                if (xmlTextWriter != null) xmlTextWriter.Close();
            }
            return xmlSerializedString;
        }

        /// <summary>
        /// Serialize an object to a file.
        /// </summary>
        /// <param name="model">The object you want to serialize.</param>
        /// <param name="fileName">The name of the file you want to serialize to.</param>
        public void SerializeToFile(Object model, string fileName)
        {
            var fileHelper = new FileHelper();
            fileHelper.EnsureParentDirExists(fileName);

            using (var writer = new StreamWriter(fileName))
            {
                try
                {
                    var serializer = new XmlSerializer(model.GetType());
                    serializer.Serialize(writer, model);
                }
                catch (Exception e)
                {
                    FeedbackProvider(e.Message);
                }
            }
        }


        /// <summary>
        /// Deserializes an object.
        /// </summary>
        /// <param name="model">The object you want to Deserialize</param>
        /// <returns>The deserialized object.</returns>
        public T Deserialize<T>(String xmlSerializedObject)
        {
            T model = default(T);
            MemoryStream memoryStream = null;
            try
            {
                var xs = new XmlSerializer(typeof(T));
                memoryStream = new MemoryStream(StringToUTF8ByteArray(xmlSerializedObject));
                model = (T)xs.Deserialize(memoryStream);
            }
            catch (Exception e)
            {
                FeedbackProvider(e.Message);
            }
            finally
            {
                if (memoryStream != null) memoryStream.Dispose();
            }
            return model;
        }
        public T Deserialize<T>(XElement xmlSerializedObject)
        {
            return Deserialize<T>(xmlSerializedObject.ToString());
        }

        /// <summary>
        /// Deserializes an object.
        /// </summary>
        /// <param name="model">The object you want to Deserialize</param>
        /// <returns>The deserialized object.</returns>
        public T DeserializeFromFile<T>(string fileName)
        {
            var fileHelper = new FileHelper();
            fileHelper.EnsureParentDirExists(fileName);

            T model = default(T);
            var fileStream = new FileStream(fileName, FileMode.OpenOrCreate);
            try
            {
                var serializer = new XmlSerializer(typeof(T));
                model = (T)serializer.Deserialize(fileStream);
            }
            catch (Exception e)
            {
                FeedbackProvider(e.Message);
            }
            finally
            {
                fileStream.Dispose();
            }
            return model;
        }
        #endregion

        #region Byte[]/String Conversions
        /// <summary>
        /// Converts UTF8 Byte Array to a string. This is used in the serialize 
        /// method so I can write to a memory stream instead of a file.
        /// </summary>
        public String UTF8ByteArrayToString(Byte[] characters)
        {
            var encoding = new UTF8Encoding();
            var constructedString = encoding.GetString(characters);
            return (constructedString);
        }

        /// <summary>
        /// Converts a string to a UTF8 Byte Array. This is This is used in the 
        /// deserialize method so I can read from a memory stream instead of a file.
        /// </summary>
        public Byte[] StringToUTF8ByteArray(String pXmlString)
        {
            var encoding = new UTF8Encoding();
            var byteArray = encoding.GetBytes(pXmlString);
            return byteArray;
        }
        #endregion
    }
}
